package torres.entity;

import java.awt.image.BufferedImage;
import java.util.LinkedHashSet;
import java.util.Random;
import torres.controller.Direction;
import torres.item.EquippableItem;
import torres.item.EquippedInventory;
import torres.item.Inventory;
import torres.item.Item;
import torres.item.TakeableItem;
import torres.item.TrapItem;
import torres.item.Weapon;
import torres.item.WeaponType;
import torres.ability.Ability;
import torres.ability.ActionAbility;
import torres.ability.Cost;
import torres.ability.NonActionAbility;
import torres.action.Effect;
import torres.entity.Occupation.Job;
import torres.fileIO.BasicContainer;
import torres.fileIO.ClassContainer;
import torres.fileIO.Saveable;
import torres.map.Location;
import torres.map.World;
import torres.view.Displayable;
import torres.utility.Statistics;
import torres.view.ImageLoader;

/**
 * @author Torres
 * An entity is a mobile thing which has a specific location and the 
 * ability to relocate itself. Entities are characterized by stats.
 * Can be either player-controlled or computer-controlled.
 */
public abstract class Entity implements Displayable, Saveable {	
	protected String name;						// Image name used for rendering
	protected String displayName;				// Actual name of Entity
	protected Occupation job;
	protected int currHealth;
	protected int currMana;
	protected int lives;
	protected int intellect;
	protected int strength;
	
	protected int agility;
	protected int hardiness;
	protected int experience;
	protected int experienceReq;
	protected int movement; 
	protected int level;
	protected int skillPoints;
	protected Location location;
	protected Direction direction;
	protected int bindWound;
	protected int bargain;
	protected int observation;
	protected Random random;
	protected Effect elementType;
	protected int gold;
	
	protected int influenceRadius;
	
	protected int fireResistance;
	protected int waterResistance;
	protected int airResistance;
	protected int lightningResistance;

	protected Inventory inventory;
	protected EquippedInventory equipInventory;
	
	protected World world = World.getInstance();
	protected ImageLoader loader;
	protected String imageName;
	protected BufferedImage image;

	protected boolean interacting;

	protected boolean visible;

	public abstract boolean doMoreChecking(Location location);
	public abstract void interact(Entity e);
	
	/**
	 * Creates a new default Entity.
	 */
	public Entity(){
		
		lives = 0;
		level = 1;
		experience = 0;
		experienceReq = 1;
		direction = Direction.EAST;
		observation = 1;
		this.influenceRadius = 2;
		job = new Smasher();
		this.setHardiness((int)(10 * this.getOccupation().getStrengthMultiplier()));
		this.setIntellect((int)(10 * this.getOccupation().getIntellectMultiplier()));
		this.setStrength((int)(10 * this.getOccupation().getStrengthMultiplier()));
		this.setAgility((int)(10 * this.getOccupation().getAgilityMultiplier()));
		this.setMovement((int)(5 * this.getOccupation().getAgilityMultiplier()));
		skillPoints = 20;
		this.setCurrHealth(getMaxHealth());
		this.setCurrMana(getMaxMana());
		
		this.setAirResistance(1);
		
		this.setFireResistance(1);
		this.setLightningResistance(1);
		this.setWaterResistance(1);
		
		random = new Random();
		this.setVisible();
		inventory = new Inventory();
		equipInventory = new EquippedInventory();
		updateAbilityList(level);
		this.elementType = Effect.PHYSICAL;
	}
	
	/**
	 * Creates a new Entity with given occupation.
	 * @param occupation Occupation job for Entity
	 */
	public Entity (Occupation occupation) {
		
		lives = 0;
		level = 1;
		experience = 0;
		experienceReq = 1;
		observation = 1;
		direction = Direction.EAST;
		this.influenceRadius = 2;
		job = occupation;
		this.setHardiness((int)(10 * this.getOccupation().getStrengthMultiplier()));
		this.setIntellect((int)(10 * this.getOccupation().getIntellectMultiplier()));
		this.setStrength((int)(10 * this.getOccupation().getStrengthMultiplier()));
		this.setAgility((int)(10 * this.getOccupation().getAgilityMultiplier()));
		this.setMovement((int)(5 * this.getOccupation().getAgilityMultiplier()));
		this.setCurrHealth(getMaxHealth());
		this.setCurrMana(getMaxMana());
		skillPoints = 20;
		this.setAirResistance(1);
		this.setFireResistance(1);
		this.setLightningResistance(1);
		this.setWaterResistance(1);
		
		random = new Random();
		this.setVisible();
		inventory = new Inventory();
		equipInventory = new EquippedInventory();
		updateAbilityList(1);
		this.elementType = Effect.PHYSICAL;
	}
	
	/**
	 * Creates a new Entity with given attributes.
	 * @param name Name of Entity
	 * @param job Occupation of Entity
	 * @param elementType Elemental type of Entity
	 */
	public Entity(String name, Occupation job, Effect elementType){
		
		this(job);
		this.name = name;
		this.elementType = elementType; 
	}
	
	/**
	 * Creates a new Entity with given attributes.
	 * @param name Name of image of NPC
	 * @param imageName Name of image of NPC with direction specified
	 * @param job Occupation of NPC
	 * @param intellect Intellect of NPC
	 * @param strength Strength of NPC
	 * @param agility Agility of NPC
	 * @param hardiness Hardiness of NPC
	 * @param movement Movement of NPC
	 * @param level Level of NPC
	 * @param bindWound Level of bindWound skill
	 * @param bargain Level of bargain skill
	 * @param observation Level of observation skill
	 * @param influenceRadius influenceRadius of NPC
	 * @param fireRes Fire elemental resistance of NPC
	 * @param waterRes Water elemental resistance of NPC
	 * @param airRes Air elemental resistance of NPC
	 * @param lightningRes Lightning elemental resistance of NPC
	 * @param location Location of NPC
	 * @param elementType Elemental type of the NPC
	 */
	public Entity(String name, String imageName, Occupation job, int intellect, int strength, int agility, 
			int hardiness, int movement, int level, int bindWound, int bargain, int observation, 
			int influenceRadius,int fireRes, int waterRes, int airRes, int lightningRes, 
			Location location, Effect elementType) {
		this(job);
		this.setName(imageName);
		this.setImageName(imageName + "E.png");
		this.setDisplayName(name);
		this.setIntellect(intellect);
		this.setStrength(strength);
		this.setAgility(agility);
		this.setHardiness(hardiness);
		this.setMovement(movement);
		this.setLevel(level);
		this.setBindWound(bindWound);
		this.setBargain(bargain);
		this.setObservation(observation);
		this.setInfluenceRadius(influenceRadius);
		this.setFireResistance(fireRes);
		this.setWaterResistance(waterRes);
		this.setAirResistance(airRes);
		this.setLightningResistance(lightningRes);
		this.setLocation(location);
		this.setElementType(elementType);
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString(){
		
		return "Entity's name is: " + name + " currlife = " + currHealth;
	}
	
	
	/*Accessor methods */
	
	/**
	 * Return Name of Entity.
	 * @return the Name of the Image of Entity
	 */
	public String getName() {
		
		return name;
	}
	
	/**
	 * Return Display Name of Entity.
	 * @return the Name of the Entity
	 */
	public String getDisplayName() {
		
		return displayName;
	}
	
	/**
	 * Return Entity's job class.
	 * @return Entity's Occupation
	 */
	public Occupation getOccupation() {
		
		return job;
	}
	
	public int getObservation() {
		return observation;
	}
	/*Accessor methods for life and mana*/
	
	/**
	 * Return Entity's current health.
	 * @return Current Health of Entity
	 */
	public int getCurrHealth() {
		
		return currHealth;
	}
	
	/**
	 * Return Entity's current mana.
	 * @return Current Mana of Entity
	 */
	public int getCurrMana() {
		
		return currMana;
	}
	
	/*Accessor methods for basic stats*/
	
	/**
	 * Return Entity's number of lives.
	 * @return Number of Lives of Entity
	 */
	public int getLives() {
		
		return lives;
	}
	
	/**
	 * Return Entity's current intellect.
	 * @return Intellect of Entity
	 */
	public int getIntellect(){
		
		return intellect;
	}
	
	/**
	 * Return Entity's current strength.
	 * @return Strength of Entity 
	 */
	public int getStrength(){
		
		return strength;
	}
	
	/**
	 * Return Entity's current agility.
	 * @return Agility of Entity 
	 */
	public int getAgility(){
		
		return agility;
	}
	
	/**
	 * Return Entity's current hardiness.
	 * @return Hardiness of Entity 
	 */
	public int getHardiness(){
		
		return hardiness;
	}
	
	/**
	 * Return Entity's current experience. 
	 * @return Experience of Entity 
	 */
	public int getExperience(){
		
		return experience;
	}
	
	/**
<<<<<<< .mine
	 * @return Experience of Entity 
	 */
	public int getExperienceReq() {
		
		return experienceReq = (int) Math.pow(this.level, 2.5);
	}
	
	/**
	 * Return Entity's current movement cost.
	 * @return Movement cost of Entity 
	 */
	public int getMovement(){
		
		return movement;
	}
	
	/**
	 * Return Entity's current level. 
	 * @return Level of Entity 
	 */
	public int getLevel(){
		
		return level;
	}
	
	/**
	 * Return Entity's current location.
	 * @return Location of Entity 
	 */
	public Location getLocation(){
		
		return location;
	}
	
	/**
	 * Return Entity's current direction.
	 * @return Entity Direction
	 */
	public Direction getDirection() {
		
		return direction;
	}
	
	/**
	 * Return Entity's inventory.
	 * @return Inventory
	 */
	public Inventory getInventory() {
		
		return inventory;
	} 
	
	/**
	 * Return Entity's equippable inventory.
	 * @return EquippedInventory of Entity 
	 */
	public EquippedInventory getEquippedInventory(){
		
		return equipInventory;
	}
	
	/*Accessor methods for drawing*/
	
	/**
	 * Return Entity's current image loader.
	 * @return ImageLoader of Entity
	 */
	public ImageLoader getLoader(){
		
		return loader;
	}
	
	/**
	 * Return Entity's current buffered image. 
	 * @return BufferedImage of Entity
	 */
	public BufferedImage getImage() {
		return image;
	}
	
	/**
	 * Return Entity's current image name. 
	 * @return ImageName of Entity 
	 */
	public String getImageName(){
		return imageName;
	}
	
	/*Derived stats accessors. These may need something to accomodate 
	 increased derived stat items*/
	
	/**
	 * Return Entity's current max health.
	 * @return Max Health of Entity
	 */
	public int getMaxHealth() {
		return (int) (1.5*level+1.2*hardiness);
	}
	
	/**
	 * Return Entity's current max mana. 
	 * @return Max Mana of Entity
	 */
	public int getMaxMana() {
		return (int) (1.5*level + Math.sqrt(intellect));
	}
	
	/**
	 * Return Entity's offensive rating.
	 * @return Offensive Rating of Entity
	 */
	public int getOffensiveRating() {
		
		double smasherBonus = 1;
		double summonerBonus = 1;
		double sneakBonus = 1;
		if(this.getOccupation().getJob().equals(Job.SMASHER)) {
			
			if( this.getEquippedInventory().getLeftHandItem() != null && this.getEquippedInventory().getRightHandItem() != null){
				if((((Weapon)this.getEquippedInventory().getLeftHandItem()).getWeaponType().equals(WeaponType.TWO_HANDED))){
					
					smasherBonus = (double)((Smasher)this.getOccupation()).getTwoHandedSkillBonus() * .01;
				}
			}
			
			else if(this.getEquippedInventory().getLeftHandItem() != null && ((Weapon)this.getEquippedInventory().getLeftHandItem()).getWeaponType().equals(WeaponType.ONE_HANDED)){
				
				smasherBonus = (double)((Smasher)this.getOccupation()).getOneHandedSkillBonus() * .01;
			}
			
			else if(this.getEquippedInventory().getLeftHandItem() == null || (((Weapon)this.getEquippedInventory().getLeftHandItem()).getWeaponType().equals(WeaponType.BRAWLING))){
				
				smasherBonus = (double)((Smasher)this.getOccupation()).getBrawlSkillBonus() * .01;
			}
			
			return (int)(((1.5*level + 1.2*strength) + equipInventory.getWeaponTotal()) * smasherBonus);
		}
		
		else if(this.getOccupation().getJob().equals(Job.SUMMONER)){
			
			if(this.getEquippedInventory().getLeftHandItem() != null){
				
				if(((Weapon)this.getEquippedInventory().getLeftHandItem()).getWeaponType().equals(WeaponType.STAFF))
					summonerBonus = (double)((Summoner)this.getOccupation()).getStaffSkillBonus() * .01;
			}
			return (int)(((1.5*level + 1.2*strength) + equipInventory.getWeaponTotal()) * summonerBonus);
		}
		
		else if(this.getOccupation().getJob().equals(Job.SNEAK)){
			
			if(this.getEquippedInventory().getLeftHandItem() != null){
				
				if(((Weapon)this.getEquippedInventory().getLeftHandItem()).getWeaponType().equals(WeaponType.RANGED))
					sneakBonus = (double)((Sneak)this.getOccupation()).getRangedWeaponSkillBonus() * .01;
			}
			return (int)(((1.5*level + 1.2*strength) + equipInventory.getWeaponTotal()) * sneakBonus);
		}
	
		else
		{
			return (int)(((1.5*level + 1.2*strength) + equipInventory.getWeaponTotal()));
		}
	}
	
	/**
	 * Return Entity's defensive rating. 
	 * @return Defensive Rating of Entity
	 * 
	 */
	public int getDefensiveRating() {
		//System.out.println("Entity: DEFENSIVE RATING " + (int)((1.5*level + 1.2*agility)));
		return (int) (1.5*level + 1.2*agility);
	}
	
	/**
	 * Return Entity's current armor rating. 
	 * @return Armor Rating of Entity
	 */
	public int getArmorRating() {
		//System.out.println("Entity: ARMOR RATING " +  (int)((1.2*hardiness) +  equipInventory.getArmorTotal()));
		return (int) (1.2*hardiness) +  equipInventory.getArmorTotal();
	}
	
	/*Set methods*/
	
	/**
	 * Sets the Name of this image of Entity
	 * @param new name of Entity to set
	 */
	public void setName(String name){
		this.name = name;
	}
	
	/**
	 * Sets the Name of Entity
	 * @param new value of display name to set
	 */
	public void setDisplayName(String displayName){
		this.displayName = displayName;
	}
	
	/**
	 * Sets the Job of this Entity
	 * @param new occupation of job to set
	 */
	public void setJob(Occupation job){
		this.job = job;
	}
	
	/**
	 * Sets the Current Health of this Entity
	 * @param new value of current health to set
	 */
	public void setCurrHealth(int currHealth){	
		if(currHealth<=0) {
			this.currHealth = this.getMaxHealth();
			setLives(getLives() - 1);
		}
		else if(currHealth > this.getMaxHealth())
			this.currHealth = this.getMaxHealth();
		else
			this.currHealth = currHealth;
	}
	
	/**
	 * Sets the Current Mana of this Entity
	 * @param new value of current mana to set
	 */
	public void setCurrMana(int currMana){		
		if(currHealth < 0)
			this.currMana = this.getMaxMana();
		else if(currMana > this.getCurrMana())
			this.currMana = this.getMaxMana();
		else
			this.currMana = currMana;
	}
	
	/*Mutator methods for basic stats*/
	
	/**
	 * Sets the Lives of this Entity
	 * @param new value of lives to set
	 */
	public void setLives(int lives){
		
		if(lives <= 0) 
			this.lives = 0;
		else
			this.lives = lives;
	}
	
	/**
	 * Sets the Intellect of this Entity
	 * @param new value of intellect to set
	 */
	public void setIntellect(int intellect){
		
		this.intellect = intellect;
	}
	
	/**
	 * Sets the Strength of this Entity
	 * @param new value of strength to set
	 */
	public void setStrength(int strength){
		
		this.strength = strength;
	}
	
	/**
	 * Sets the Agility of this Entity.
	 * @param new value of agility to set
	 */
	public void setAgility(int agility){
		
		this.agility = agility;
	}
	
	/**
	 * Sets the Hardiness of this Entity.
	 * @param new value of hardiness to set
	 */
	public void setHardiness(int hardiness){
		
		this.hardiness = hardiness;
	}
	
	/**
	 * Sets the Experience of this Entity
	 * @param new value of experience to set
	 */
	public void setExperience(int experience){
		
		this.experience = experience;
	}
	
	/**
	 * Sets the Movement cost of this Entity
	 * @param new value of movement to set
	 */
	public void setMovement(int movement){
		
		this.movement = movement;
	}
	
	/**
	 * Sets the Level of this Entity
	 * @param new value of level to set
	 */
	public void setLevel(int level){
		
		this.level = level;
	}
	
	/**
	 * Sets the Location of this Entity
	 * @param new location of entity to set
	 */
	public void setLocation(Location location){
		
		this.location = location;
	}
	
	/**
	 * Sets the Direction of this Entity
	 * @param new direction of entity to set
	 */
	public void setDirection(Direction direction){
		
		this.direction = direction;
	}
	
	/**
	 * Sets the ImageName of this Entity
	 * @param new ImageName of Entity to set
	 */
	public void setImageName(String imageName){
		this.imageName = imageName;
	}
	
	/**
	 * Sets the ImageLoader of this Entity
	 * @param new ImageLoader of entity to set
	 */
	public void setLoader(ImageLoader loader){
		
		this.loader = loader;
	}
	
	/**
	 * Sets the Image of this Entity
	 * @param new BufferedImage of entity to set
	 */
	public void setImage(BufferedImage image){
		
		this.image = image;
	}
	
	/* Health related methods for Entity*/
	
	/**
	 * Decreases Entity Current Health by Amount and Armor Rating
	 * @param int value for Amount of Health to Decrement
	 * @return Damage done
	 */
	public int takeDamage(int amount) {
		
//		int armorRating = this.getArmorRating();
	//	armorRating /= 10;
//		amount -= armorRating;
		currHealth -= amount;
		
		if(currHealth <= 0) {
			takeLife();
		}
		return amount;
	}
	
	/**
	 * Increases Entity Current Health by Amount
	 * @param amount int value for Amount of Health to Increment
	 */
	public void healDamage(int amount) {
		
		currHealth += amount;
		
		if(currHealth > getMaxHealth()) {
			
			currHealth = getMaxHealth();
		}
	}
	
	/**
	 * Increases Entity Current Mana by Amount
	 * @param amount int value for Amount of Mana to Increment
	 */
	public void healMana(int amount) {
		currMana += amount;
		
		if(currMana > getMaxMana())
			currMana = getMaxMana();
	}
	/**
	 * Decreases Entity Current Mana by Amount
	 * @param int value for Amount of Mana to Decrement
	 */
	public void decCurrMana(int amount) {
		
		this.setCurrMana( this.getCurrMana() - amount);
	}
	
	/**
	 * Increases Entity Current Mana by Amount
	 * @param int value for Amount of Mana to Increment
	 */
	public void incCurrMana(int amount) {
		this.setCurrMana(this.getCurrMana() + amount);
	}
	
	/**
	 * Increases Entity Current Experience by Amount
	 * @param int value for Amount of Experience to Increment
	 */
	public void incExp(int amount) {
		
		experience += amount;
	}
	
	/**
	 * Adds item into Inventory 
	 * @param Takeable item for Entity's Inventory to Add
	 */
	public boolean addItem(TakeableItem item) {
		
		return inventory.addItem(item);
	}
	
	/**
	 * Removes item into Inventory 
	 * @param Takeable item for Entity's Inventory to Remove
	 */
	public TakeableItem removeItem(TakeableItem item) {
		return inventory.removeItem(item);
	}
	
	
	public void useItem(TakeableItem item) {
		inventory.removeItem(item);
		item.apply(this);
	}
	
	/**
	 * Equips item into EquippedInventory 
	 * @param Takeable item for Entity's EquippedInventory to Equip
	 */
	public void equipItem(EquippableItem item) {
		if(item.addToEquipInventory(equipInventory)){
		//if(equipInventory.addItem(item)) {
			inventory.removeItem(item);
		}
		else System.out.println("CANNOT EQUIP ITEM");
	}
	
	public void equipWeapon(Weapon w) {
		if(this.getOccupation().canEquip(w.getWeaponType()) && w.addToEquipInventory(equipInventory)) {
		//if(equipInventory.addWeapon(w)) {
			inventory.removeItem(w);
		}
		else System.out.println("CANNOT EQUIP WEAPON");
	}
	
	/**
	 * Removes item into EquippedInventory 
	 * @param Takeable item for Entity's EquippedInventory to Remove
	 */
	public EquippableItem unEquipItem(EquippableItem item) {
		
		if(!inventory.isFull()) {
			inventory.addItem(item);
			item.removeFromEquipInventory(equipInventory);
			//equipInventory.removeItem(item);
			return item;
		}
		else
			return null;
	}
	
	public Weapon unEquipWeapon(Weapon w) {
		if(!inventory.isFull()) {
			inventory.addItem(w);
			w.removeFromEquipInventory(equipInventory);
			//equipInventory.removeWeapon(w);
			return w;
		}
		else
			return null;
	}
	/**
	 * Checks if item is in Inventory 
	 * @param Takeable item for check in Inventory
	 */
	public boolean hasItem(TakeableItem item) {
		return (inventory.hasItem(item) || equipInventory.hasItem(item));
	}
	
	/**
	 * Return amount of gold in wallet. 
	 * @return Entity's gold
	 */
	public int getGold() {
		return gold;
	}
	
	/**
	 * Decrements Entity's gold by specified amount.
	 * @param gold Amount to decrement gold by
	 */
	public void decGold(int gold){
		this.gold -= gold;
		if(this.gold < 0)
			gold = 0;
	}
	
	/**
	 * Increments Entity's gold by specified amount.
	 * @param gold Amount to increment gold by
	 */
	public void incGold(int gold){
		if(gold < 0 )
			gold = 0;
		this.gold += gold;
	}
	
	/**
	 * Buys specified item for specified cost. 
	 * @param item TakeableItem to be purchased
	 * @param cost Purchase price
	 * @return true if bought, else false
	 */
	public boolean buyItem(TakeableItem item, int cost){
		
		boolean bought = false;
		if((inventory.getCurrentSize() < inventory.getMaxSize()) &&
				(this.getGold() >= cost)){
			
			inventory.addItem(item);
			decGold(cost);
			return bought;
		}
		
		return bought; 
	}
	
	/**
	 * Sells specified item for specified price. Price determined by bargain of both Entities
	 * @param item TakeableItem to be sold
	 * @param agreedPrice Price to be sold for
	 */
	public void sellItem(TakeableItem item, int agreedPrice){
		
		inventory.removeItem(item);
		this.incGold(agreedPrice);
	}
	
	/* Getters and setters for Skills */
	
	/**
	 * Changes the BindWound for Entity
	 * @param new value of bindWound level to set
	 */
	public void setBindWound(int bindWound){
		
		this.bindWound = bindWound;
	}
	
	/**
	 * Changes the Bargain for Entity
	 * @param new value of bargain level to set
	 */
	public void setBargain(int bargain){
		
		this.bargain = bargain;
	}
	
	/**
	 * Changes the Observation for Entity
	 * @param new value of observation level to set
	 */
	public void setObservation(int observation){
		
		this.observation = observation;
	}
	
	/**
	 * Return Entity's current level of bindWound.
	 * @return BindWound value of Entity
	 */
	public int getBindWound(){
		
		return bindWound;
	}
	
	/**
	 * Return Entity's current level of bargain.
	 * @return Bargain value of Entity
	 */
	public int getBargain(){
		
		return bargain;
	}
	
	public double getBargainBonus()
	{
		return ((bargain * .01));
	}
	
	/**
	 * Return Entity's current level of fire resistance.
	 * @return Fire Resistance value of Entity
	 */
	public int getFireResistance(){
		
		return fireResistance; 
	}
	
	/**
	 * Return Entity's current level of water resistance.
	 * @return Water Resistance value of Entity
	 */
	public int getWaterResistance(){
		
		return waterResistance; 
	}
	
	/**
	 * Return Entity's current level of air resistance.
	 * @return Air Resistance value of Entity
	 */
	public int getAirResistance(){
		
		return airResistance; 
	}
	
	/**
	 * Return Entity's current level of lightning resistance.
	 * @return Lightning Resistance value of Entity
	 */
	public int getLightningResistance(){
		
		return lightningResistance; 
	}
	
	/**
	 * Sets the Fire Resistance value of Entity.
	 * @param new value of fire resistance to set
	 */
	public void setFireResistance(int fireResistance){
		
		this.fireResistance = fireResistance;
	}
	
	/**
	 * Sets the Water Resistance value of Entity.
	 * @param new value of water resistance to set
	 */
	public void setWaterResistance(int waterResistance){
		
		this.waterResistance = waterResistance;
	}
	
	/**
	 * Sets the Air Resistance value of Entity.
	 * @param new value of air resistance of entity to set
	 */
	public void setAirResistance(int airResistance){
		
		this.airResistance = airResistance;
	}
	
	/**
	 * Sets the Lightning Resistance value of Entity.
	 * @param new value of lightning resistance to set
	 */
	public void setLightningResistance(int lightningResistance){
		
		this.lightningResistance = lightningResistance;
	}
	
	/**
	 * Updates level; called when Entity steps on a Level-up Tile.
	 */
	public void updateLevel(){
		int currLevel = level;
		int newLevel = (int) (Math.pow(experience,(1/2.5))+1);
		//experienceReq = (int) (Math.pow(currLevel+1, 2.5) - Math.pow(currLevel, 2.5));
		
		int levelDiff = newLevel - currLevel;
		if( levelDiff > 0 ) {
			intellect = (int) (intellect*job.getIntellectMultiplier()*levelDiff);
			strength = (int) (strength*job.getStrengthMultiplier()*levelDiff);
			agility = (int) (agility*job.getAgilityMultiplier()*levelDiff);
			//this.setHardiness((int)(double)(10 * Math.exp(newLevel/100) * this.getOccupation().getStrengthMultiplier()*levelDiff));
			hardiness += (int)((Math.exp((newLevel*3)/65) + 0.5) * this.getOccupation().getStrengthMultiplier()*levelDiff);
			movement += (int)((Math.exp((newLevel*3)/300) + 0.5) * this.getOccupation().getAgilityMultiplier());
			//skillPoints += (int)((Math.exp((newLevel*3)/300) + 1));
			skillPoints += (int)(((Math.exp((newLevel*3)/300)) + 1) * levelDiff);
			System.out.println("Entity: updateLevel(): Level: " + newLevel + " SkillPoints: " + this.getSkillPoints());
			this.setLevel(newLevel);
			this.currHealth = getMaxHealth();
			this.currMana = getMaxMana();
			updateAbilityList(newLevel);
		}
	}
	
	/**
	 * Updates Entity's ability list based on given level.
	 * @param level Level used to update ability list
	 */
	public void updateAbilityList(int level) {
		this.job.updateAbilityList(level);
		//HashSet<Ability> arr = this.job.getAbilityList();
		//System.out.println("Entity: updateLevel(): " + arr.isEmpty());
		//for (Ability a : arr) 
			//System.out.println("Entity: updateAbilityList(): Level: " + level + " Ability: " + a.toString());
	}
	
	/**
	 * Decrements Entity's current lives left by one. 
	 */
	public void takeLife() {
		
		lives--;
		if(lives < 0)
			this.actionUponDeath();
		currHealth = this.getMaxHealth();
	}
	
	/**
	 * Moves the entity in specified direction on map.
	 * @param d Direction to move.
	 * @return true if moved, else false. 
	 */
	public boolean move(Direction d) {
		String oldImageName = imageName;
		
		imageName = name + d.toString().charAt(0) + ".png";
		
		this.setDirection(d);
		
		if(imageName.compareTo(oldImageName) != 0) {
			
			loader = new ImageLoader(imageName);
			image = loader.getImage();
		}
		
		int incX = d.deltaX();
		int incY = d.deltaY();
		
		int oldX = location.getX();
		int oldY = location.getY();
			
		this.moveHook();
		
		Location destination = Location.locationInDirection(location, d);
		if(checkLocation(destination)) /*checkLocation can change the current location */ {	
			if(location.getX() == oldX && location.getY() == oldY) {
				
				world.removeEntity(location);
				world.addEntity(this, destination);
				
				location.setX(oldX + incX);
				location.setY(oldY + incY);
			}
			if(world.hasItem(destination)) { 
				Item i = world.getItem(destination);
				i.onTouch(this);
				if(i.isConsumed())
					world.removeItem(destination);
			}
			else if(world.hasTrap(destination)) {
				TrapItem t = world.getTrap(destination);
				t.onTouch(this);
				if(t.isConsumed())
					world.removeTrap(destination);
			}
			return true;
		}
		else {
				
			return false;
		}
	}
	
	public void moveHook() {
		//this method left intentionally (almost) blank
	}
	
	/**
	 * Checks to see if given location is a moveable location.
	 * @param location Location to be checked
	 * @return true if moveable, else false
	 */
	public boolean checkLocation(Location location) {
		if(world.isValidLocation(location) && doMoreChecking(location) && !world.isOccupied(location)) {
			
			return true;
		}
		else {
			
			return false;
		}
	}
	
	/**
	 * Entity takes effect with specified attributes.
	 * @param element Elemental type of effect
	 * @param accuracy Accuracy of effect
	 * @param damage Amount of damage of effect
	 * @return value of damage actually taken by Entity
	 */

    public int takeEffect(Effect element, int accuracy, int damage) {
        
        if(element != Effect.PHYSICAL && this.elementType.equals(element) || element == Effect.HEAL) {
        	this.healDamage(damage);
        	return damage;
        }
        else if(element == Effect.MANA){
        	this.healMana(damage);
        	return damage;
        }
        else if(element == Effect.BUFF_FIRE) {
        	this.incFireRes(damage);
        	return damage;
        }
        else {
                
            int resistance = 1;
            switch(element) {
                    case FIRE       : resistance = fireResistance; break;
                    case WATER      : resistance = waterResistance; break;
                    case AIR        : resistance = airResistance; break;
                    case LIGHTNING  : resistance = lightningResistance; break;
                    case PHYSICAL   : resistance = 1; break;
                    case SLEEP      : //use the line below
                    case CHARM      : this.takeEffectHook(element, damage, accuracy); return 0;
            }
            
            int value = 0;
            if(Statistics.attempt(agility, accuracy)) {
                    value = takeDamage(resistance * damage);
            }
            return value;
        }
    }

	public void incFireRes(int damage) {
		this.fireResistance += damage;
	}
	
	public void takeEffectHook(Effect element, int accuracy, int damage) {
		//this method intentionally left (almost) blank
	}
	
	/**
	 * Just before entity dies, does some action
	 * @return experience entity gives to another entity that killed it. 
	 */
	public int actionUponDeath() {	
		
		World.getInstance().getPlayer().incExp(experience);
		World.getInstance().removeEntity(this.getLocation());
		return experience; 
	}
	
	/**
	 * Entity tries to execute an ActionAbility.
	 * @param ability ActionAbility entity tried
	 */
	public void attemptActionAbility(ActionAbility ability) {
		//if (this.attemptAbility(ability))
			ability.useAbility(this);
	}
	
	/**
	 * Entity tries to execute a NonActionAbility.
	 * @param ability NonActionAbility entity tried
	 */
	public void attemptNonActionAbility(NonActionAbility ability) {
		//if (this.attemptAbility(ability))
			ability.useAbility(this);
	}
	
	/**
	 * Checks to see if entity can use ability.
	 * @param ability Ability to be checked
	 * @return true if can be used, else false
	 */
	private boolean attemptAbility(Ability ability) {
		/*This code checks if ability can be attempted
		 * EX:
		 * currLife = 10
		 * cost.getStat = CURRHEALTH
		 * cost.getCost = 3, 10 > 3
		 * safeToUse = true
		 */
		
		boolean safeToUse = true;
		for(Cost cost : ability.getCosts()) {
			switch(cost.getStat()) {
				case CURRHEALTH : 
					if (cost.getCost() > this.currHealth)
						safeToUse = false;
					break;
				case CURRMANA :
					if (cost.getCost() > this.currMana)
						safeToUse = false;
					break;
				case LIVES :
					if (cost.getCost() > this.lives)
						safeToUse = false;
					break;
				case INTELLECT :
					if (cost.getCost() > this.intellect)
						safeToUse = false;
					break;
				case STRENGTH :
					if (cost.getCost() > this.strength)
						safeToUse = false;
					break;
				case AGILITY :
					if (cost.getCost() > this.agility)
						safeToUse = false;
					break;
				case HARDINESS :
					if (cost.getCost() > this.hardiness)
						safeToUse = false;
					break;
				case EXPERIENCE :
					if (cost.getCost() > this.experience)
						safeToUse = false;
					break;
				case MOVEMENT :
					if (cost.getCost() > this.movement)
						safeToUse = false;
					break;
				case LEVEL :
					if (cost.getCost() > this.level)
						safeToUse = false;
					break;
				case BINDWOUND :
					if (cost.getCost() > this.bindWound)
						safeToUse = false;
					break;
				case OBSERVATION :
					if (cost.getCost() > this.observation)
						safeToUse = false;
					break;
				case SKILLPOINTS :
					if(cost.getCost()>this.skillPoints)
						safeToUse = false;
					break;
				default :
					System.err.println("Entity: attemptAbility(): Error: Statistic used is not checked for cost: " + cost.getStat());
			}
			if (!safeToUse)
				return false;
		}
		System.out.println("SAFE TO USE");
		//Apply costs
		for(Cost cost : ability.getCosts()) {
			switch(cost.getStat()) {
				case CURRHEALTH : 
					this.currHealth -= cost.getCost();
					break;
				case CURRMANA :
					this.currMana -= cost.getCost();
					break;
				case LIVES :
					this.lives -= cost.getCost();
					break;
				case INTELLECT :
					this.intellect -= cost.getCost();
					break;
				case STRENGTH :
					this.strength -= cost.getCost();
					break;
				case AGILITY :
					this.agility -= cost.getCost();
					break;
				case HARDINESS :
					this.hardiness -= cost.getCost();
					break;
				case EXPERIENCE :
					this.experience -= cost.getCost();
					break;
				case MOVEMENT :
					this.movement -= cost.getCost();
					break;
				default :
					System.err.println("Entity: safeToUse(): Error: Statistic cost not applied: " + cost.getStat());
			}
		}
		return safeToUse;  
	}
	
	/**
	 * Returns value entity has stolen from another entity
	 * @param toSteal value entity tried to steal
	 * @return value entity actually stole
	 */
	public int steal(int toSteal) {
		int ret = Math.min(toSteal, this.getGold());
		this.decGold(ret);
		return ret;
	}
	
	/**
	 * Returns check of if Entity is interacting with other entities.
	 * @return true if entity is interacting, else false
	 */
	public boolean isInteracting() {
		
		return interacting;
	}
	
	/**
	 * Sets entity true if interacting else false. 
	 * @param new value of interacting to set
	 */
	public void setInteracting(boolean interacting) {
		
		this.interacting = interacting;
	}
	
	/**
	 * Return entity's influence radius
	 * @return influenceRadius
	 */
	public int getInfluenceRadius() {
		return this.influenceRadius + (this.observation/3);
	}
	
	/**
	 * Sets entity's influence radius.
	 * @param new value of influence radius to set
	 */
	protected void setInfluenceRadius(int influenceRadius) {
		this.influenceRadius = influenceRadius;
	}
	
	/**
	 * Returns entity's visibility.
	 * @return visibility of Entity.
	 */
	public boolean isVisible() {
		return this.visible;
	}
	
	/**
	 * Makes entity invisible.
	 */
	public void setInvisible() {
		this.visible = false;
	}
	
	/**
	 * Makes entity visible.
	 */
	public void setVisible() {
		this.visible = true;
	}
	
	/* (non-Javadoc)
	 * @see torres.fileIO.Saveable#save()
	 */
	public ClassContainer save() {
		
		ClassContainer curClass = new ClassContainer("Entity");
		if(job!=null) curClass.addProperty(job.save());
		if(inventory!=null) curClass.addProperty(inventory.save());
		if(equipInventory!=null) curClass.addProperty(equipInventory.save());
		if(location!=null)curClass.addProperty(location.save());
		
		curClass.addProperty(new BasicContainer("experience", experience));
		curClass.addProperty(new BasicContainer("experienceReq", experienceReq));
		curClass.addProperty(new BasicContainer("hardiness", hardiness));
		curClass.addProperty(new BasicContainer("agility", agility));
		curClass.addProperty(new BasicContainer("intellect", intellect));
		curClass.addProperty(new BasicContainer("strength", strength));
		curClass.addProperty(new BasicContainer("name", name));
		if(displayName!=null)curClass.addProperty(new BasicContainer("displayName", displayName));
		curClass.addProperty(new BasicContainer("level", level));
		curClass.addProperty(new BasicContainer("imageName", imageName));
		curClass.addProperty(new BasicContainer("movement", movement));
		curClass.addProperty(new BasicContainer("direction", direction));
		curClass.addProperty(new BasicContainer("currHealth", currHealth));
		curClass.addProperty(new BasicContainer("currMana", currMana));
		curClass.addProperty(new BasicContainer("bindWound", bindWound));
		curClass.addProperty(new BasicContainer("bargain", bargain));
		curClass.addProperty(new BasicContainer("observation", observation));
		curClass.addProperty(new BasicContainer("lives", lives));
		curClass.addProperty(new BasicContainer("skillPoints", skillPoints));
		curClass.addProperty(new BasicContainer("influenceRadius", influenceRadius));
		curClass.addProperty(new BasicContainer("fireResistance", fireResistance));
		curClass.addProperty(new BasicContainer("waterResistance", waterResistance));
		curClass.addProperty(new BasicContainer("airResistance", airResistance));
		curClass.addProperty(new BasicContainer("lightningResistance", lightningResistance));
		curClass.addProperty(new BasicContainer("interacting", interacting));
		curClass.addProperty(new BasicContainer("visible", visible));
		curClass.addProperty(new BasicContainer("gold", gold));
		
		//protected Random random;
		
		return curClass;
	}

	
	/**
	 * @return
	 */
	public int getSkillPoints() {
		return this.skillPoints;
	}
	/**
	 * @return
	 */
	private boolean canUseSkillPoints() {
		return this.skillPoints > 0;
	}
	/**
	 * @param skill
	 * @return
	 */
	public boolean allocateSkillPoint(Skill skill) {
		boolean successfulAllocate = true;
		if (canUseSkillPoints()) {
			switch(skill) {
				case BINDWOUNDS : 
					if(bindWound<35)
						this.bindWound++; 
					else 
						successfulAllocate=false;
						break;
				case BARGAIN : 
					if(bargain<35)
						this.bargain++;
					else 
						successfulAllocate=false;
					break;
				case OBSERVATION : 
					if(observation<35)
						this.observation++;
					else 
						successfulAllocate=false;
					break;
				default : successfulAllocate = this.job.allocateSkillPoint(skill); break;
			}
			if (successfulAllocate)
				this.skillPoints--;
		}
		return successfulAllocate;
	}
	
	public LinkedHashSet<String> retreiveSkillList(LinkedHashSet<String> skillList) {
		skillList.add("Bind Wounds Skill:\t\t\t" + this.bindWound);
		skillList.add("Bargain Skill:\t\t\t\t" + this.bargain);
		skillList.add("Observation Skill:\t\t\t" + this.observation);
		this.job.retreiveSkillList(skillList);
		return skillList;
	}
	
	public LinkedHashSet<Skill> retreiveSkillEnumList(LinkedHashSet<Skill> hashSet) {
		// TODO Auto-generated method stub
		hashSet.add(Skill.BINDWOUNDS);
		hashSet.add(Skill.BARGAIN);
		hashSet.add(Skill.OBSERVATION);
		return job.retreiveSkillEnumList(hashSet);
	}

	/* (non-Javadoc)
	 * @see torres.fileIO.Saveable#load(torres.fileIO.ClassContainer)
	 */
	public void load(ClassContainer property) {
		ClassContainer jobCont = (ClassContainer)property.getProperty("Job");
		if((jobCont.getProperty("sneakAgilityMultiplier"))!=null)
			job = new Sneak();
		else if((jobCont.getProperty("smasherAgilityMultiplier"))!=null)
			job = new Smasher();
		else if((jobCont.getProperty("summonerAgilityMultiplier"))!=null)
			job = new Summoner();
		job.load(jobCont);
		ClassContainer inventoryCont = (ClassContainer)property.getProperty("Inventory");
		inventory = new Inventory();
		inventory.load(inventoryCont);
		ClassContainer equipInventoryCont = (ClassContainer)property.getProperty("EquippedInventory");
		equipInventory.load(equipInventoryCont);
		ClassContainer locationCont = (ClassContainer)property.getProperty("Location");
		if(locationCont!=null) {
			location = new Location(0,0);
			location.load(locationCont);
		}
		
		BasicContainer nameCont = (BasicContainer)property.getProperty("name");
		name = (String)nameCont.getValue();
		BasicContainer imageNameCont = (BasicContainer)property.getProperty("imageName");
		imageName = (String)imageNameCont.getValue();
		BasicContainer movementCont = (BasicContainer)property.getProperty("movement");
		movement = Integer.parseInt((String)movementCont.getValue());
		BasicContainer experienceCont = (BasicContainer)property.getProperty("experience");
		experience = Integer.parseInt((String)experienceCont.getValue());
		BasicContainer experienceReqCont = (BasicContainer)property.getProperty("experienceReq");
		experienceReq = Integer.parseInt((String)experienceReqCont.getValue());
		BasicContainer skillPointsCont = (BasicContainer)property.getProperty("skillPoints");
		skillPoints = Integer.parseInt((String)skillPointsCont.getValue());
		BasicContainer hardinessCont = (BasicContainer)property.getProperty("hardiness");
		hardiness = Integer.parseInt((String)hardinessCont.getValue());
		BasicContainer agilityCont = (BasicContainer)property.getProperty("agility");
		agility = Integer.parseInt((String)agilityCont.getValue());
		BasicContainer intellectCont = (BasicContainer)property.getProperty("intellect");
		intellect = Integer.parseInt((String)intellectCont.getValue());
		BasicContainer strengthCont = (BasicContainer)property.getProperty("strength");
		intellect = Integer.parseInt((String)strengthCont.getValue());
		BasicContainer directionCont = (BasicContainer)property.getProperty("direction");
		direction = Direction.valueOf((String)directionCont.getValue());
		BasicContainer currHealthCont = (BasicContainer)property.getProperty("currHealth");
		currHealth = Integer.parseInt((String)currHealthCont.getValue());
		BasicContainer currManaCont = (BasicContainer)property.getProperty("currMana");
		currMana = Integer.parseInt((String)currManaCont.getValue());
		BasicContainer livesCont = (BasicContainer)property.getProperty("lives");
		lives = Integer.parseInt((String)livesCont.getValue());
		BasicContainer levelCont = (BasicContainer)property.getProperty("level");
		level = Integer.parseInt((String)levelCont.getValue());
		BasicContainer bindWoundCont = (BasicContainer)property.getProperty("bindWound");
		bindWound = Integer.parseInt((String)bindWoundCont.getValue());
		BasicContainer bargainCont = (BasicContainer)property.getProperty("bargain");
		bargain = Integer.parseInt((String)bargainCont.getValue());
		BasicContainer observationCont = (BasicContainer)property.getProperty("observation");
		observation = Integer.parseInt((String)observationCont.getValue());
		BasicContainer influenceRadiusCont = (BasicContainer)property.getProperty("influenceRadius");
		influenceRadius = Integer.parseInt((String)influenceRadiusCont.getValue());
		BasicContainer fireResistanceCont = (BasicContainer)property.getProperty("fireResistance");
		fireResistance = Integer.parseInt((String)fireResistanceCont.getValue());
		BasicContainer waterResistanceCont = (BasicContainer)property.getProperty("waterResistance");
		waterResistance = Integer.parseInt((String)waterResistanceCont.getValue());
		BasicContainer airResistanceCont = (BasicContainer)property.getProperty("airResistance");
		airResistance = Integer.parseInt((String)airResistanceCont.getValue());
		BasicContainer lightningResistanceCont = (BasicContainer)property.getProperty("lightningResistance");
		lightningResistance = Integer.parseInt((String)lightningResistanceCont.getValue());
		BasicContainer interactingCont = (BasicContainer)property.getProperty("interacting");
		interacting = Boolean.parseBoolean((String)interactingCont.getValue());
		BasicContainer visibleCont = (BasicContainer)property.getProperty("visible");
		visible = Boolean.parseBoolean((String)visibleCont.getValue());
		BasicContainer goldCont = (BasicContainer)property.getProperty("gold");
		gold = Integer.parseInt((String)goldCont.getValue());
		BasicContainer displayNameCont = (BasicContainer)property.getProperty("displayName");
		displayName = (String)displayNameCont.getValue();
		BasicContainer elementTypeCont = (BasicContainer)property.getProperty("elementType");
		if(elementTypeCont!=null)
			elementType = Effect.valueOf((String)elementTypeCont.getValue());	
	}
	
	/**
	 * Returns entity's elemental type.
	 * @return elementalType of Entity
	 */
	public Effect getElementType() {
		return this.elementType;
	}
	/**
	 * Sets entity's elemental type. 
	 * @param new value of elemental type to set
	 */
	protected void setElementType(Effect elementType) {
		this.elementType = elementType;
	}
}

